home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 4 / Info_Mac IV CD-ROM (Pacific HiTech Inc.)(August 1994).iso / Development / Source / Chess++ ƒ / CBrain.cp < prev    next >
Text File  |  1993-05-26  |  17KB  |  543 lines

  1. ////////////
  2. //
  3. //    CBrain.cp
  4. //
  5. //    The Brain Class ( or The Virtual Opponent )
  6. //
  7. //    Class which represents all thought processes, heuristics, and logic,
  8. //    which are used in calculating, and responding to the real player's move.
  9. //
  10. //    SUPERCLASS = CBureaucrat
  11. //    Copyright © 1993 Steven J. Bushell. All rights reserved.
  12. //
  13. ////////////
  14.  
  15. #include <Global.h>
  16. #include <Commands.h>
  17. #include "ChessCommands.h"
  18. #include "CBrain.h"
  19. #include "CChessDoc.h"
  20. #include <CBartender.h>
  21. #include <CApplication.h>
  22.  
  23. #include <stdio.h>
  24.  
  25. // Global Variables
  26. extern    CApplication        *gApplication;    /* Application object        */
  27. extern    CBartender        *gBartender;    /* The menu handling object */
  28. extern    CBureaucrat        *gGopher;        /* First in line to get commands    */
  29. extern    CChessBoard        *gChessBoard;
  30.  
  31.  
  32. void    CBrain::IBrain(CBureaucrat    *aSupervisor)
  33. {
  34.     isBrainsMove = false;
  35.     isThinking = false;
  36.     searchDepth = 1;
  37.     soundOnBetterMoves = false;
  38.     showContemplatedMoves = false;
  39.     backgroundTimeInterval = 500;
  40.                                         /* Initialize superclass    */
  41.     CBureaucrat::IBureaucrat(aSupervisor);
  42. }
  43.  
  44.  
  45. // When the boolean flag 'isBrainsMove' is true, the Brain will start processing
  46. // the best responding move, occasionally giving time to the rest of the program.
  47. // Once it has found the best move, it executes the move, and sets the flag
  48. // 'isBrainsMove' back to false, and returns control back to the other player.
  49. void    CBrain::Dawdle(long    *maxSleep)
  50. {
  51.     if (isBrainsMove)
  52.     {    
  53.         // if this is our first time through for this move, initialize
  54.         // all Brain class variables and find best response
  55.         if (!isThinking)
  56.         {
  57.             ClearThoughts();
  58.             Think();
  59.         }
  60.     }
  61. }
  62.  
  63.  
  64. void    CBrain::Think(void)
  65. {
  66.     CChessPiece    *testPiece;
  67.     CChessBoard    *theRealBoardCopy;
  68.     short        testPieceColor;
  69.     Boolean        showSelectedMoves;
  70.     Rect            aRect;
  71.     short        savedBSCR ,savedBSCF;
  72.  
  73.     theRealBoardCopy = (CChessBoard *)theRealBoard->Copy();
  74.  
  75.     for(testFile=1;testFile<=8;testFile++)
  76.         for(testRank=1;testRank<=8;testRank++) 
  77.         {
  78.             showSelectedMoves = showContemplatedMoves;
  79.             
  80.             if (!isThinking)
  81.                 break;
  82.             
  83.             testPiece = theRealBoard->theBoard[testRank][testFile];
  84.             testPieceColor = (testPiece) ? testPiece->itsColor : NoColor;
  85.  
  86.             if (testPieceColor == theBrainsColor) {
  87.                 register short    secondTestRank, secondTestFile;
  88.                 long            totalMoveValue;
  89.                 CChessPiece    *targetPiece;
  90.                 short        targetPieceColor;
  91.  
  92.                 if (showSelectedMoves) {
  93.                     theRealBoard->itsChessPane->Prepare();
  94.                     SetRect(&aRect,(testRank-1)<<5,(testFile-1)<<5,testRank<<5,testFile<<5);
  95.                     InvertRect(&aRect);
  96.                 }
  97.  
  98.                 theRealBoardCopy->firstClickRank = testRank;
  99.                 theRealBoardCopy->firstClickFile = testFile;
  100.         
  101.                 for(secondTestFile=1;secondTestFile<=8;secondTestFile++)
  102.                     for(secondTestRank=1;secondTestRank<=8;secondTestRank++)
  103.                     {
  104.                         totalMoveValue = 0;
  105.         
  106.                         targetPiece = theRealBoardCopy->theBoard[secondTestRank][secondTestFile];
  107.                         targetPieceColor = (targetPiece) ? targetPiece->itsColor: NoColor;
  108.  
  109.                         if ((targetPieceColor != theBrainsColor) &&
  110.                             !((testRank == secondTestRank) && (testFile == secondTestFile)) &&
  111.                             (testPiece->IsValidMove(theRealBoardCopy,secondTestRank,secondTestFile)))
  112.                         {
  113.                             // find out what this particular move would be worth
  114.                             totalMoveValue = FindMoveValue(theRealBoardCopy,secondTestRank,secondTestFile);
  115.  
  116.                             if (totalMoveValue > bestMoveValue)
  117.                             {
  118.                                 if (soundOnBetterMoves)
  119.                                     SysBeep(0);
  120.                                 bestMoveValue = totalMoveValue;
  121.                                 bestFirstClickRank = testRank;
  122.                                 bestFirstClickFile = testFile;
  123.                                 bestSecondClickRank = secondTestRank;
  124.                                 bestSecondClickFile = secondTestFile;
  125.                             }
  126.                         }
  127.                     }
  128.                         
  129.                     if (showSelectedMoves)
  130.                     {
  131.                         theRealBoard->itsChessPane->Prepare();
  132.                         InvertRect(&aRect);
  133.                         InvalRect(&aRect);
  134.                     }
  135.             }
  136.         }
  137.  
  138.     if (!bestFirstClickRank)    // if no valid moves were found, the Brain will resign
  139.     {
  140.         InitCursor();
  141.         NoteAlert(ResignNoteAlert,0L);
  142.         theRealBoard->gameOver = true;
  143.         goto EXIT;
  144.     }
  145.  
  146.     // register Brain's move on the real board
  147.     theRealBoard->firstClickRank = bestFirstClickRank;
  148.     theRealBoard->firstClickFile = bestFirstClickFile;
  149.     theRealBoard->RegisterMove(bestSecondClickRank,bestSecondClickFile);
  150.  
  151.     savedBSCR = bestSecondClickRank;
  152.     savedBSCF = bestSecondClickFile;
  153.  
  154.     //    check to see if the Brain has won the game
  155.     if (CheckForMate())
  156.     {
  157.         theRealBoard->gameOver = true;
  158.         InitCursor();
  159.         if (CheckForStalemate())
  160.             NoteAlert(StalemateNoteAlert,0L);
  161.         else
  162.             NoteAlert(CheckmateNoteAlert,0L);
  163.     }
  164.  
  165.     theRealBoard->itsChessPane->Prepare();
  166.     theRealBoard->itsChessPane->ShowMove(savedBSCR,savedBSCF);
  167.  
  168. EXIT:
  169.     // return control to the other player
  170.     isBrainsMove = false;
  171.     isThinking = false;
  172.     theRealBoard->itsChessPane->BecomeGopher(true);
  173.     theRealBoardCopy->Dispose();
  174.     
  175.     // Return application to 'normal' idling speed
  176.     gApplication->cMaxSleepTime = 2;
  177.  
  178.     // if we're swapping players, just leave the board alone and get out
  179.     if (abortMove)
  180.         return;
  181. }
  182.  
  183.  
  184. // Initialize the Brain to start planning a new move.
  185. void    CBrain::ClearThoughts(void)
  186. {
  187.     theRealBoard = gChessBoard;
  188.     theBrainsColor = theRealBoard->theOtherPlayersColor;
  189.     bestMoveValue = -0x1000;
  190.     testRank = 1;
  191.     testFile = 1;
  192.     bestFirstClickRank = 0;
  193.     bestFirstClickFile = 0;
  194.     bestSecondClickRank = 0;
  195.     bestSecondClickFile = 0;
  196.     backgroundTimeIntervalTimer = 0L;
  197.     isThinking = true;
  198.     abortMove = false;
  199.     
  200.     // We want the Brain to think fast while we're waiting
  201.     gApplication->cMaxSleepTime = 0;
  202. }
  203.  
  204.  
  205. // Begin recursion to find total worth of this particular subjunctive move
  206. short    CBrain::FindMoveValue(CChessBoard *theRealBoard, short secondTestRank, short secondTestFile)
  207. {
  208.     CChessPiece     *thePieceToMove = theRealBoard->
  209.                         theBoard[theRealBoard->firstClickRank][theRealBoard->firstClickFile],
  210.                 *theTargetPiece = theRealBoard->theBoard[secondTestRank][secondTestFile];
  211.     short        theTargetPieceValue = 0;
  212.     long            tempMoveValue = -0x0000;
  213.     CChessBoard    *firstThoughtBoard;
  214.     
  215.     firstThoughtBoard = theRealBoard;
  216.  
  217.     if (theTargetPiece)
  218.         theTargetPieceValue = theTargetPiece->itsValue;
  219.  
  220.     tempMoveValue += theTargetPieceValue;
  221.         
  222.     tempMoveValue += thePieceToMove->BoardLocationValue(firstThoughtBoard, secondTestRank,secondTestFile);
  223.  
  224.     tempMoveValue -= FindBestVirtualResponse(firstThoughtBoard,firstThoughtBoard->myColor,
  225.                                         secondTestRank,secondTestFile,searchDepth);
  226.  
  227.     return tempMoveValue;
  228. }
  229.  
  230.  
  231. // Recurse virtual boards CBrain::searchDepth times, returning all best possible responses 
  232. short    CBrain::FindBestVirtualResponse(CChessBoard *theWorkingBoard,
  233.                     short theColor, short secondClickRank,
  234.                     short secondClickFile, short theSearchDepth)
  235. {
  236.     CChessBoard    *aVirtualBoard;
  237.     register        virtualTestRank,virtualTestFile,virtualSecondTestRank,virtualSecondTestFile;
  238.     long            thisVirtualResponse,bestVirtualResponse = -0x2000;
  239.     short        thePieceToMoveColor,theTargetPieceColor;
  240.     CChessPiece     *thePieceToMove,*theTargetPiece;
  241.     CChessPiece    *savedTestPiece,*savedTargetPiece;
  242.     short        savedFirstClickRank,savedFirstClickFile;
  243.     short        savedLastFirstClickRank,savedLastFirstClickFile;
  244.     short        savedLastSecondClickRank,savedLastSecondClickFile;
  245.     short        savedMyColor,savedTheOtherPlayersColor;
  246.     short        savedWhiteKingRank,savedWhiteKingFile,
  247.                 savedBlackKingRank,savedBlackKingFile;
  248.  
  249.     if (!theSearchDepth || abortMove)
  250.     {
  251.         if (++backgroundTimeIntervalTimer>backgroundTimeInterval)
  252.         {
  253.             backgroundTimeIntervalTimer = 0;
  254.             gApplication->Process1Event();
  255.         }
  256.         return 0;
  257.     }
  258.  
  259.     aVirtualBoard = theWorkingBoard;
  260.     savedTestPiece = aVirtualBoard->
  261.                 theBoard[aVirtualBoard->firstClickRank][aVirtualBoard->firstClickFile];
  262.     savedTargetPiece = aVirtualBoard->
  263.                 theBoard[secondClickRank][secondClickFile];
  264.     savedFirstClickRank = aVirtualBoard->firstClickRank;
  265.     savedFirstClickFile = aVirtualBoard->firstClickFile;
  266.     savedLastFirstClickRank = aVirtualBoard->lastFirstClickRank;
  267.     savedLastFirstClickFile = aVirtualBoard->lastFirstClickFile;
  268.     savedLastSecondClickRank = aVirtualBoard->lastSecondClickRank;
  269.     savedLastSecondClickFile = aVirtualBoard->lastSecondClickFile;
  270.     savedMyColor = aVirtualBoard->myColor;
  271.     savedTheOtherPlayersColor = aVirtualBoard->theOtherPlayersColor;
  272.     savedWhiteKingRank = aVirtualBoard->whiteKingRank;
  273.     savedWhiteKingFile = aVirtualBoard->whiteKingFile;
  274.     savedBlackKingRank = aVirtualBoard->blackKingRank;
  275.     savedBlackKingFile = aVirtualBoard->blackKingFile;
  276.  
  277.     aVirtualBoard->RegisterMove(secondClickRank,secondClickFile);
  278.  
  279.     // swap colors on virtual board to think like opponent
  280.     aVirtualBoard-> theOtherPlayersColor = aVirtualBoard->myColor;
  281.     aVirtualBoard->myColor = (aVirtualBoard->myColor == White) ? Black : White;
  282.  
  283.     // pick a piece to move 
  284.     for (virtualTestFile=1;virtualTestFile<=8;virtualTestFile++)
  285.         for (virtualTestRank=1;virtualTestRank<=8;virtualTestRank++)
  286.         {
  287.             thePieceToMove = aVirtualBoard->theBoard[virtualTestRank][virtualTestFile];
  288.             if (thePieceToMove)
  289.                 thePieceToMoveColor = thePieceToMove->itsColor;
  290.             else
  291.                 thePieceToMoveColor = NoColor;
  292.  
  293.             if (thePieceToMoveColor == aVirtualBoard->theOtherPlayersColor)
  294.             {
  295.                 aVirtualBoard->firstClickRank = virtualTestRank;
  296.                 aVirtualBoard->firstClickFile = virtualTestFile;
  297.                 
  298.                 // pick a target piece or square to move to
  299.                 for (virtualSecondTestFile=1;virtualSecondTestFile<=8;virtualSecondTestFile++)
  300.                     for (virtualSecondTestRank=1;virtualSecondTestRank<=8;virtualSecondTestRank++)
  301.                     {
  302.                         theTargetPiece = aVirtualBoard->theBoard[virtualSecondTestRank][virtualSecondTestFile];
  303.                         theTargetPieceColor = (theTargetPiece) ? theTargetPiece->itsColor : NoColor;
  304.                         
  305.                         if((virtualTestRank == virtualSecondTestRank) && (virtualTestFile == virtualSecondTestFile))
  306.                             continue;
  307.                         
  308.                         thisVirtualResponse = (theTargetPiece) ? theTargetPiece->itsValue : 0;
  309.                         
  310.                         // if it's one of my pieces, add on its value as protection points
  311.                         if (theTargetPieceColor == aVirtualBoard->theOtherPlayersColor)
  312.                             ;
  313.                         else
  314.                         // is this a valid piece/square to move to?
  315.                         if (thePieceToMove->IsValidMove(aVirtualBoard,virtualSecondTestRank,virtualSecondTestFile))
  316.                         {
  317.                             thisVirtualResponse += thePieceToMove->
  318.                                         BoardLocationValue(aVirtualBoard,
  319.                                             virtualSecondTestRank,virtualSecondTestFile);
  320.                     
  321.                             // the value of this response is the captured piece/square value
  322.                             // minus the responding move's value
  323.                             //    NOTE: this line is the single most important one in this entire
  324.                             //    program.  It recursively handles all of the subjunctive-move
  325.                             //    lookup calculations.  Amazing, isn't it?
  326.                             thisVirtualResponse  -= FindBestVirtualResponse(aVirtualBoard,theColor,
  327.                                         virtualSecondTestRank,virtualSecondTestFile,theSearchDepth-1);
  328.                             
  329.                             if (thisVirtualResponse > bestVirtualResponse)
  330.                                 bestVirtualResponse = thisVirtualResponse;
  331.                         }
  332.                     }
  333.             }
  334.         }
  335.  
  336.     aVirtualBoard->theBoard[savedFirstClickRank][savedFirstClickFile] = savedTestPiece;
  337.     aVirtualBoard->theBoard[secondClickRank][secondClickFile] = savedTargetPiece;
  338.     aVirtualBoard->firstClickRank = savedFirstClickRank;
  339.     aVirtualBoard->firstClickFile = savedFirstClickFile;
  340.     aVirtualBoard->lastFirstClickRank = savedLastFirstClickRank;
  341.     aVirtualBoard->lastFirstClickFile = savedLastFirstClickFile;
  342.     aVirtualBoard->lastSecondClickRank = savedLastSecondClickRank;
  343.     aVirtualBoard->lastSecondClickFile = savedLastSecondClickFile;
  344.     aVirtualBoard->myColor = savedMyColor;
  345.     aVirtualBoard->theOtherPlayersColor = savedTheOtherPlayersColor;
  346.     aVirtualBoard->whiteKingRank = savedWhiteKingRank;
  347.     aVirtualBoard->whiteKingFile = savedWhiteKingFile;
  348.     aVirtualBoard->blackKingRank = savedBlackKingRank;
  349.     aVirtualBoard->blackKingFile = savedBlackKingFile;
  350.  
  351.     return bestVirtualResponse;
  352. }
  353.  
  354. Boolean    CBrain::CheckForMate(void)
  355. {
  356.     CChessPiece    *testPiece;
  357.     CChessBoard    *theRealBoardCopy;
  358.     long            savedBackgroundTimeInterval = backgroundTimeInterval;
  359.     short        testPieceColor,
  360.                 savedBrainsColor = theBrainsColor,
  361.                 savedSearchDepth = searchDepth;
  362.     Boolean        showSelectedMoves;
  363.     Rect            aRect;
  364.  
  365.     //    do a simplified version of ClearThoughts()
  366.     theBrainsColor = theRealBoard->myColor;
  367.     bestMoveValue = -0x1000;
  368.     testRank = 1;
  369.     testFile = 1;
  370.     bestFirstClickRank = 0;
  371.     bestFirstClickFile = 0;
  372.     bestSecondClickRank = 0;
  373.     bestSecondClickFile = 0;
  374.     backgroundTimeInterval = 0x10000000;
  375.  
  376.     theRealBoardCopy = (CChessBoard *)theRealBoard->Copy();
  377.  
  378.     //    swap colors on virtual board to think like opponent
  379.     theRealBoardCopy-> theOtherPlayersColor = theRealBoardCopy->myColor;
  380.     theRealBoardCopy->myColor = (theRealBoardCopy->myColor == White) ? Black : White;
  381.  
  382.     //    prepare for superficial search for a king capture
  383.     searchDepth = 1;
  384.  
  385.     for(testFile=1;testFile<=8;testFile++)
  386.         for(testRank=1;testRank<=8;testRank++) 
  387.         {
  388.             showSelectedMoves = showContemplatedMoves;
  389.             
  390.             testPiece = theRealBoard->theBoard[testRank][testFile];
  391.             testPieceColor = (testPiece) ? testPiece->itsColor : NoColor;
  392.  
  393.             if (testPieceColor == theBrainsColor) {
  394.                 register short    secondTestRank, secondTestFile;
  395.                 long            totalMoveValue;
  396.                 CChessPiece    *targetPiece;
  397.                 short        targetPieceColor;
  398.  
  399.                 if (showSelectedMoves) {
  400.                     theRealBoard->itsChessPane->Prepare();
  401.                     SetRect(&aRect,(testRank-1)<<5,(testFile-1)<<5,testRank<<5,testFile<<5);
  402.                     InvertRect(&aRect);
  403.                 }
  404.  
  405.                 theRealBoardCopy->firstClickRank = testRank;
  406.                 theRealBoardCopy->firstClickFile = testFile;
  407.         
  408.                 for(secondTestFile=1;secondTestFile<=8;secondTestFile++)
  409.                     for(secondTestRank=1;secondTestRank<=8;secondTestRank++)
  410.                     {
  411.                         totalMoveValue = 0;
  412.         
  413.                         targetPiece = theRealBoardCopy->theBoard[secondTestRank][secondTestFile];
  414.                         targetPieceColor = (targetPiece) ? targetPiece->itsColor: NoColor;
  415.  
  416.                         if ((targetPieceColor != theBrainsColor) &&
  417.                             !((testRank == secondTestRank) && (testFile == secondTestFile)) &&
  418.                             (testPiece->IsValidMove(theRealBoardCopy,secondTestRank,secondTestFile)))
  419.                         {
  420.                             // find out what this particular move would be worth
  421.                             totalMoveValue = FindMoveValue(theRealBoardCopy,secondTestRank,secondTestFile);
  422.  
  423.                             if (totalMoveValue > bestMoveValue)
  424.                             {
  425.                                 if (soundOnBetterMoves)
  426.                                     SysBeep(0);
  427.                                 bestMoveValue = totalMoveValue;
  428.                                 bestFirstClickRank = testRank;
  429.                                 bestFirstClickFile = testFile;
  430.                                 bestSecondClickRank = secondTestRank;
  431.                                 bestSecondClickFile = secondTestFile;
  432.                             }
  433.                         }
  434.                     }
  435.                         
  436.                     if (showSelectedMoves)
  437.                     {
  438.                         theRealBoard->itsChessPane->Prepare();
  439.                         InvertRect(&aRect);
  440.                         InvalRect(&aRect);
  441.                     }
  442.             }
  443.         }
  444.  
  445.     theRealBoardCopy->Dispose();
  446.  
  447.     //    restore our original settings
  448.     theBrainsColor = savedBrainsColor;
  449.     searchDepth = savedSearchDepth;
  450.     backgroundTimeInterval = savedBackgroundTimeInterval;
  451.  
  452.     if (!bestFirstClickRank)    // if no valid moves were found, the Brain has checkmated its opponent!
  453.         return true;
  454.  
  455.     return false;    //    if we've made it this far, the game goes on...
  456. }
  457.  
  458.  
  459. Boolean    CBrain::CheckForStalemate(void)
  460. {
  461.     CChessPiece    *testPiece;
  462.     CChessBoard    *theRealBoardCopy;
  463.     long            savedBackgroundTimeInterval = backgroundTimeInterval;
  464.     short        testPieceColor,
  465.                 savedBrainsColor = theBrainsColor,
  466.                 savedSearchDepth = searchDepth;
  467.     Boolean        stalemate = true;
  468.  
  469.     //    do a simplified version of ClearThoughts()
  470.     backgroundTimeInterval = 0x10000000;
  471.  
  472.     theRealBoardCopy = (CChessBoard *)theRealBoard->Copy();
  473.  
  474.     for(testFile=1;testFile<=8;testFile++)
  475.         for(testRank=1;testRank<=8;testRank++) 
  476.         {
  477.             testPiece = theRealBoard->theBoard[testRank][testFile];
  478.             testPieceColor = (testPiece) ? testPiece->itsColor : NoColor;
  479.  
  480.             if (testPieceColor == theBrainsColor) {
  481.                 register short    secondTestRank, secondTestFile;
  482.                 CChessPiece    *targetPiece;
  483.                 short        targetPieceColor;
  484.  
  485.                 theRealBoardCopy->firstClickRank = testRank;
  486.                 theRealBoardCopy->firstClickFile = testFile;
  487.         
  488.                 for(secondTestFile=1;secondTestFile<=8;secondTestFile++)
  489.                     for(secondTestRank=1;secondTestRank<=8;secondTestRank++)
  490.                     {
  491.                         targetPiece = theRealBoardCopy->theBoard[secondTestRank][secondTestFile];
  492.                         if (!targetPiece)
  493.                             continue;
  494.                         targetPieceColor = targetPiece->itsColor;
  495.  
  496.                         if ((targetPieceColor != theBrainsColor) &&
  497.                             !((testRank == secondTestRank) && (testFile == secondTestFile)) &&
  498.                             (testPiece->IsValidMove(theRealBoardCopy,secondTestRank,secondTestFile)))
  499.                         {
  500.                             stalemate = false;
  501.                             goto EXIT;
  502.                         }
  503.                     }
  504.             }
  505.         }
  506.  
  507.     theRealBoardCopy->Dispose();
  508.  
  509.     //    restore our original settings
  510.     backgroundTimeInterval = savedBackgroundTimeInterval;
  511. EXIT:
  512.     return stalemate;    //    if we've made it this far, it must have been a stalemate...
  513. }
  514.  
  515.  
  516. //    the next two functions are somewhat superfluous, but left
  517. //    around for historical reasons
  518. CChessBoard *CBrain::CopyBoard(CChessBoard *theBoardToCopy)
  519. {
  520.     CChessBoard    *theVirtualBoardCopy;
  521.  
  522.     theVirtualBoardCopy = (CChessBoard *)theBoardToCopy->Copy();
  523.     
  524.     return theVirtualBoardCopy;
  525. }
  526.  
  527.  
  528. void    CBrain::DisposeBoard(CChessBoard *theBoardToDispose)
  529. {
  530.     theBoardToDispose->Dispose();
  531. }
  532.  
  533.  
  534. void    CBrain::UpdateMenus(void)
  535. {
  536.     inherited::UpdateMenus();
  537.  
  538.     // If the Brain is thinking, we don't want the user interrupting
  539.     // with anything but StopThinking, Swap Players, or the Options dialog
  540.     gBartender->EnableCmd(cmdSwapPlayers);
  541.     gBartender->EnableCmd(cmdStopThinking);
  542.     gBartender->EnableCmd(cmdOptions);
  543. }